package com.jysz.webcore.ssh.orm.hibernate;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hibernate.FlushMode;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.springframework.dao.DataAccessResourceFailureException;
import org.springframework.orm.hibernate3.SessionFactoryUtils;
import org.springframework.orm.hibernate3.SessionHolder;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;


public class OpenSessionNoTransactionFilter implements Filter {
	
	protected final Log logger = LogFactory.getLog(getClass());

	private String[] sessionFactoryName = new String[]{"sessionFactory"}; 
	private FlushMode flushMode = FlushMode.MANUAL;
	private ServletContext servletContext;

	/**
	 * Specify the Hibernate FlushMode to apply to this filter's
	 * {@link org.hibernate.Session}. Only applied in single session mode.
	 * <p>Can be populated with the corresponding constant name in XML bean
	 * definitions: e.g. "AUTO".
	 * <p>The default is "NEVER". Specify "AUTO" if you intend to use
	 * this filter without service layer transactions.
	 * @see org.hibernate.Session#setFlushMode
	 * @see org.hibernate.FlushMode#NEVER
	 * @see org.hibernate.FlushMode#AUTO
	 */
	public void setFlushMode(FlushMode flushMode) {
		this.flushMode = flushMode;
	}

	/**
	 * Return the Hibernate FlushMode that this filter applies to its
	 * {@link org.hibernate.Session} (in single session mode).
	 */
	protected FlushMode getFlushMode() {
		return this.flushMode;
	}


	public void doFilter(
			HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
			throws ServletException, IOException {
		List<SessionFactory> sessionFactoryList = new ArrayList<SessionFactory>();
		for(String sf : sessionFactoryName) {
			SessionFactory sessionFactory = lookupSessionFactory(sf);
			Session session = getSession(sessionFactory);
			if(!TransactionSynchronizationManager.hasResource(sessionFactory)) {
				TransactionSynchronizationManager.bindResource(sessionFactory, new SessionHolder(session));
			}
			sessionFactoryList.add( sessionFactory );
		}
		try {
			filterChain.doFilter(request, response);
		}
		finally {
			
			for(SessionFactory sessionFactory : sessionFactoryList) {
				SessionHolder sessionHolder =
					(SessionHolder) TransactionSynchronizationManager.unbindResource(sessionFactory);
				logger.debug("Closing single Hibernate Session in OpenSessionInViewFilter");
				closeSession(sessionHolder.getSession(), sessionFactory);
			}
		}
	}

	/**
	 * Look up the SessionFactory that this filter should use.
	 * <p>The default implementation looks for a bean with the specified name
	 * in Spring's root application context.
	 * @return the SessionFactory to use
	 * @see #getSessionFactoryBeanName
	 */
	protected SessionFactory lookupSessionFactory(String sessionFactoryName) {
		if (logger.isDebugEnabled()) {
			logger.debug("Using SessionFactory '" + sessionFactoryName + "' for OpenSessionInViewFilter");
		}
		WebApplicationContext wac =
				WebApplicationContextUtils.getRequiredWebApplicationContext(servletContext);
		return (SessionFactory) wac.getBean(sessionFactoryName, SessionFactory.class);
	}

	/**
	 * Get a Session for the SessionFactory that this filter uses.
	 * Note that this just applies in single session mode!
	 * <p>The default implementation delegates to the
	 * <code>SessionFactoryUtils.getSession</code> method and
	 * sets the <code>Session</code>'s flush mode to "NEVER".
	 * <p>Can be overridden in subclasses for creating a Session with a
	 * custom entity interceptor or JDBC exception translator.
	 * @param sessionFactory the SessionFactory that this filter uses
	 * @return the Session to use
	 * @throws DataAccessResourceFailureException if the Session could not be created
	 * @see org.springframework.orm.hibernate3.SessionFactoryUtils#getSession(SessionFactory, boolean)
	 * @see org.hibernate.FlushMode#NEVER
	 */
	protected Session getSession(SessionFactory sessionFactory) throws DataAccessResourceFailureException {
		Session session = SessionFactoryUtils.getSession(sessionFactory, true);
		FlushMode flushMode = getFlushMode();
		if (flushMode != null) {
			session.setFlushMode(flushMode);
		}
		return session;
	}

	/**
	 * Close the given Session.
	 * Note that this just applies in single session mode!
	 * <p>Can be overridden in subclasses, e.g. for flushing the Session before
	 * closing it. See class-level javadoc for a discussion of flush handling.
	 * Note that you should also override getSession accordingly, to set
	 * the flush mode to something else than NEVER.
	 * @param session the Session used for filtering
	 * @param sessionFactory the SessionFactory that this filter uses
	 */
	protected void closeSession(Session session, SessionFactory sessionFactory) {
		session.flush();
		SessionFactoryUtils.closeSession(session);
	}


	@Override
	public void doFilter(ServletRequest request, ServletResponse response,
			FilterChain filterChain) throws IOException, ServletException {
		doFilter((HttpServletRequest) request, (HttpServletResponse) response, filterChain);
	}

	@Override
	public void init(FilterConfig config) throws ServletException {
		String sf = config.getInitParameter("sessionFactory");
		if(StringUtils.isNotEmpty(sf)) {
			sessionFactoryName = sf.split("\\,");
		}
		
		servletContext = config.getServletContext();
	}

	@Override
	public void destroy() {
		//do nothing
	}
}
